﻿using Fineout.ConfigShellSettings.Xml;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Xml;

namespace Fineout.ConfigShellSettings.Environment.Configuration
{
    public static class HostConfigManager
    {
        public static string ConfigFilePath = Path.Combine(HttpRuntime.AppDomainAppPath, "Config/Host.config");

        public static Dictionary<string, string> AutofacComponentAttributes = new Dictionary<string, string>
        {
            {"instance-scope", "single-instance"},
            {
                "type",
                //TODO: Replace hardcoding
                "Fineout.ConfigShellSettings.Environment.Configuration.ConfigShellSettingsManager, Fineout.ConfigShellSettings"
            },
            //TODO: Replace hardcoding
            {"service", "Orchard.Environment.Configuration.IShellSettingsManager"}
        };

        public static void EnableConfigShellSettings()
        {
            // Update Host.config, adding Autofac component specifying ConfigSettingsManager for IShellSettingsManager
            var doc = LoadConfigDocument();

            // Track changes, only save file if changes made
            var change = false;

            // Select root "configuration" element
            var config = doc.DocumentElement;
            if (config == null)
                throw new HostConfigManagerException("Unable to load XML document's root 'configuration' element.");


            //TODO: Specify configSections with "autofac" section, ala:
            //  <configSections>
            //    <section name="autofac" type="Autofac.Configuration.SectionHandler, Autofac.Configuration" />
            //  </configSections>


            // Select "autofac" node
            var autofacNode = config.SelectSingleNode("autofac");
            if (autofacNode == null)
            {
                autofacNode = doc.CreateNode(XmlNodeType.Element, "autofac", config.NamespaceURI);

                if (autofacNode.Attributes == null)
                    throw new HostConfigManagerException("Unable to create 'autofac' node attributes.");

                autofacNode.Attributes
                           .Append(XmlHelper
                                       .CreateAttribute(doc,
                                                           "defaultAssembly",
                                                           "Orchard.Framework"));

                config.AppendChild(autofacNode);
                change = true;
            }

            // Select "components" node
            var componentsNode = autofacNode.SelectSingleNode("components");
            if (componentsNode == null)
            {
                componentsNode = doc.CreateNode(XmlNodeType.Element, "components", "");
                autofacNode.AppendChild(componentsNode);
                change = true;
            }

            // Select "component" child nodes matching target component (match by 'service' attribute)
            var components = componentsNode
                .ChildNodes
                .Cast<XmlNode>()
                .Where(
                    component =>
                    component.Attributes != null &&
                    component.Attributes["service"].Value == AutofacComponentAttributes["service"])
                .ToArray();

            // If any matching components, update attributes as necessary
            if (components.Any())
            {
                foreach (var component in components)
                {
                    foreach (var attribute in AutofacComponentAttributes)
                    {
                        XmlHelper.UpdateAttribute(doc,
                                                     component,
                                                     attribute.Key,
                                                     attribute.Value,
                                                     ref change);
                    }
                }
            }
            // No matching component, so create one
            else
            {
                var newComponent = doc.CreateNode(XmlNodeType.Element, "component", "");
                foreach (var attribute in AutofacComponentAttributes)
                {
                    if (newComponent.Attributes == null)
                        throw new HostConfigManagerException("Unable to create 'component' node attributes.");

                    newComponent.Attributes
                                .Append(XmlHelper
                                            .CreateAttribute(doc,
                                                                attribute.Key,
                                                                attribute.Value));
                }
                componentsNode.AppendChild(newComponent);
                change = true;
            }

            // If any change made, save config document to file
            if (change)
                SaveConfigDocument(doc);
        }

        public static void DisableConfigShellSettings()
        {
            // Update Host.config, removing Autofac component specifying ConfigSettingsManager for IShellSettingsManager
            var doc = LoadConfigDocument();

            // Track changes, only save file if changes made
            var change = false;

            var config = doc.DocumentElement;
            if (config == null)
                return;

            // Select "autofac" node
            var autofacNode = config.SelectSingleNode("autofac");
            if (autofacNode == null)
                return;

            // Select "components" node
            var componentsNode = autofacNode.SelectSingleNode("components");
            if (componentsNode == null)
                return;

            // Select "component" child nodes matching target component (match by 'service' attribute)
            var components = componentsNode
                .ChildNodes
                .Cast<XmlNode>()
                .Where(
                    component =>
                    component.Attributes != null &&
                    component.Attributes["service"].Value == AutofacComponentAttributes["service"])
                .ToArray();

            // If any matching components, update attributes as necessary
            if (!components.Any())
                return;

            foreach (var component in components)
            {
                // Remove matching autofac component
                componentsNode.RemoveChild(component);
                change = true;
            }

            // If any change made, save config document to file
            if (change)
                SaveConfigDocument(doc);
        }

        public static XmlDocument LoadConfigDocument()
        {
            try
            {
                var doc = XmlHelper.LoadDocument(ConfigFilePath);
                return doc;
            }
            catch (Exception e)
            {
                throw new HostConfigManagerException("Host config file not found.", e);
            }
        }

        public static void SaveConfigDocument(XmlDocument doc)
        {
            try
            {
                doc.Save(ConfigFilePath);
            }
            catch (Exception e)
            {
                throw new HostConfigManagerException("Failed saving host config file.", e);
            }
        }
    }

    #region Exceptions

    public class HostConfigManagerException : Exception
    {
        public HostConfigManagerException()
        { }

        public HostConfigManagerException(string message)
            : base(message)
        { }

        public HostConfigManagerException(string message, Exception innerException)
            : base(message, innerException)
        { }
    }

    #endregion Exceptions
}
